﻿using FileSharing.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Web;

namespace FileSharing.Helpers
{
    public static class ModelHelper
    {
        public static PathInfoModel ToModel(string path, Action<IList<Exception>> errorHandler = null)
        {
            List<Exception> exceptions = errorHandler != null ? new List<Exception>() : default(List<Exception>);

            Action<IList<Exception>> consolidatedErrorHandler = errorHandler != null
                ? (errors) =>
                  {
                      if (errors != null && errors.Count > 0)
                          exceptions.AddRange(errors);
                  }
                : default(Action<IList<Exception>>);


            PathInfoModel model = null;

            DriveInfo driveInfo = null;

            var fileType = ModelHelper.GetFileType(path, out driveInfo);

            switch (fileType)
            {
                case PathType.None:
                    model = ModelHelper.GetRootModel();
                    break;
                case PathType.Drive:
                    model = driveInfo.ToModel(includeChildren: true, errorHandler: consolidatedErrorHandler);
                    break;
                case PathType.Directory:
                    model = (new DirectoryInfo(path)).ToModel(includeChildren: true, errorHandler: consolidatedErrorHandler);
                    break;
                case PathType.File:
                    model = (new FileInfo(path)).ToModel();
                    break;
                case PathType.Unknown:
                default:
                    model = ModelHelper.ToUnknownModel(path);
                    break;
            }

            if (errorHandler != null
                && exceptions != null
                && exceptions.Count > 0)
                errorHandler.Invoke(exceptions);

            return model;
        }

        private static PathInfoModel GetRootModel(Action<IList<Exception>> errorHandler = null)
        {
            IList<Exception> exceptions = errorHandler != null ? new List<Exception>() : default(IList<Exception>);

            PathInfoModel rootModel = new PathInfoModel()
            {
                DisplayName = ModelHelper.GetDisplayNameForRootPath(),
                Exists = true,
            };

            IList<DriveInfo> drives = null;
            try
            {
                drives = DriveInfo.GetDrives();
            }
            catch (Exception ex)
            {
                if (exceptions != null
                    && !exceptions.Contains(ex))
                    exceptions.Add(ex);
                else
                    throw;
            }
            if (drives != null
                && drives.Count > 0)
            {
                for (int i = 0; i < drives.Count; i++)
                {
                    var drive = drives[i].ToModel(errorHandler: errorHandler);
                    if (drive != null)
                        rootModel.Children.Add(drive);
                }
            }

            if (errorHandler != null
                && exceptions != null
                && exceptions.Count > 0)
                errorHandler.Invoke(exceptions);


            return rootModel;
        }

        private static PathInfoModel ToModel(this DriveInfo drive, bool includeChildren = false, Action<IList<Exception>> errorHandler = null)
        {
            PathInfoModel driveModel = null;

            IList<Exception> exceptions = errorHandler != null ? new List<Exception>() : default(IList<Exception>);

            if (drive != null)
            {
                driveModel = new PathInfoModel()
                {
                    FullPath = drive.IsReady ? drive.RootDirectory.FullName : drive.Name,
                    DisplayName = drive.GetDisplayName(),
                    FileType = PathType.Drive,
                    Size = drive.IsReady ? drive.TotalSize : int.MinValue,
                    ModifiedTime = drive.IsReady ? drive.RootDirectory.LastWriteTime : default(DateTime),
                    Exists = drive.IsReady,
                };

                if (drive.IsReady
                    && includeChildren)
                {
                    PathInfoModel rootDirectoryModel = null;
                    try
                    {
                        rootDirectoryModel = drive.RootDirectory.ToModel(true, errorHandler);
                    }
                    catch (Exception ex)
                    {
                        if (exceptions != null
                            && !exceptions.Contains(ex))
                            exceptions.Add(ex);
                        else
                            throw;
                    }
                    if (rootDirectoryModel != null
                        && rootDirectoryModel.Children != null
                        && rootDirectoryModel.Children.Count > 0)
                        driveModel.Children.AddRange(rootDirectoryModel.Children);

                }
            }
            if (errorHandler != null
                && exceptions.Count > 0)
                errorHandler.Invoke(exceptions);
            return driveModel;
        }

        private static PathInfoModel ToModel(this DirectoryInfo directory, bool includeChildren = false, Action<IList<Exception>> errorHandler = null)
        {
            IList<Exception> exceptions = errorHandler != null ? new List<Exception>() : default(IList<Exception>);

            PathInfoModel directoryModel = null;

            if (directory != null)
            {
                directoryModel = new PathInfoModel()
                {
                    FullPath = directory.FullName,
                    DisplayName = directory.GetDisplayName(),
                    FileType = PathType.Directory,
                    Exists = directory.Exists,
                    ModifiedTime = directory.Exists ? directory.LastWriteTime : default(DateTime),
                };

                if (directory.Exists && includeChildren)
                {

                    try
                    {
                        IList<DirectoryInfo> children = directory.GetDirectories();

                        if (children != null
                            && children.Count > 0)
                            for (int i = 0; i < children.Count; i++)
                            {
                                var childModel = children[i].ToModel();
                                if (childModel != null) directoryModel.Children.Add(childModel);
                            }
                        children = null;
                    }
                    catch (Exception ex)
                    {
                        if (exceptions != null)
                            exceptions.Add(ex);
                        else
                            throw;
                    }

                    try
                    {
                        IList<FileInfo> children = directory.GetFiles();
                        if (children != null
                            && children.Count > 0)
                            for (int i = 0; i < children.Count; i++)
                            {
                                var childModel = children[i].ToModel();
                                if (childModel != null)
                                    directoryModel.Children.Add(childModel);
                            }
                        children = null;
                    }
                    catch (Exception ex)
                    {
                        if (exceptions != null)
                            exceptions.Add(ex);
                        else
                            throw;
                    }
                }
            }

            if (errorHandler != null
                && exceptions != null
                && exceptions.Count > 0)
                errorHandler.Invoke(exceptions);

            return directoryModel;
        }

        private static PathInfoModel ToModel(this FileInfo file)
        {
            return file != null
                ? new PathInfoModel()
                {
                    FullPath = file.FullName,
                    DisplayName = file.GetDisplayName(),
                    FileType = PathType.File,
                    Exists = file.Exists,
                    Size = file.Length,
                    ModifiedTime = file.Exists ? file.LastWriteTime : default(DateTime),
                }
                : null;
        }

        private static PathInfoModel ToUnknownModel(string path)
        {
            return !string.IsNullOrWhiteSpace(path)
                ? new PathInfoModel()
                {
                    FullPath = path,
                    DisplayName = ModelHelper.GetDisplayNameForUnknownPath(path),
                    FileType = PathType.Unknown,
                    Exists = false,
                    Message = string.Format("Path not found: {0}", path)
                }
                : null;
        }

        public static string GetDisplayName(string path)
        {
            string displayName = string.Empty;

            DriveInfo drv = null;

            var fileType = ModelHelper.GetFileType(path, out drv);

            if (fileType == PathType.None)
                displayName = ModelHelper.GetDisplayNameForRootPath();
            else if (fileType == PathType.Drive)
            {
                displayName = drv.GetDisplayName();
                drv = null;
            }
            else if (fileType == PathType.Directory)
            {
                DirectoryInfo directory = new DirectoryInfo(path);
                displayName = directory.GetDisplayName();
                directory = null;
            }
            else if (fileType == PathType.File)
            {
                FileInfo file = new FileInfo(path);
                displayName = file.GetDisplayName();
                file = null;
            }
            else if (fileType == PathType.Unknown)
                displayName = ModelHelper.GetDisplayNameForUnknownPath(path);
            return displayName;
        }

        public static string GetDisplayNameForRootPath()
        {
            return "Storage Media";

        }

        public static string GetDisplayName(this DriveInfo drive)
        {
            string displayName = string.Empty;

            if (drive != null)
            {
                string prefix = drive.IsReady ? drive.VolumeLabel : string.Empty;
                if (string.IsNullOrWhiteSpace(prefix))
                {
                    switch (drive.DriveType)
                    {
                        case DriveType.Fixed:
                        case DriveType.Network:
                        case DriveType.Removable:
                            prefix = drive.DriveType + " Storage";
                            break;
                        case DriveType.CDRom:
                        case DriveType.Ram:
                            prefix = drive.DriveType.ToString().ToUpperInvariant();
                            break;
                        default:
                            prefix = "Unknown Media";
                            break;
                    }
                }
                displayName = string.Format("{0} ({1})", prefix, drive.Name.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
            }
            return displayName;
        }

        public static string GetDisplayName(this DirectoryInfo directoryInfo)
        {
            return directoryInfo != null && !string.IsNullOrWhiteSpace(directoryInfo.Name) ? directoryInfo.Name : "";
        }
        public static string GetDisplayName(this FileInfo fileInfo)
        {
            return fileInfo != null && !string.IsNullOrWhiteSpace(fileInfo.Name) ? fileInfo.Name : "";
        }

        public static string GetDisplayNameForUnknownPath(string path)
        {
            return (!string.IsNullOrWhiteSpace(path) ? path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).LastOrDefault() : null) ?? string.Empty;
        }

        public static PathType GetFileType(string path, out DriveInfo drive)
        {
            drive = null;
            PathType fileType = PathType.None;

            if (!string.IsNullOrWhiteSpace(path))
            {
                IList<DriveInfo> drives = DriveInfo.GetDrives();

                DriveInfo drv = null;
                Parallel.For(0, drives.Count, (index, loopState) =>
                {
                    if (drv == null
                        && !loopState.IsExceptional
                        && !loopState.ShouldExitCurrentIteration
                        && !loopState.IsStopped)
                    {
                        var d = drives != null && index >= 0 && index < drives.Count ? drives[index] : null;
                        if (d != null
                            && string.Equals(d.RootDirectory.FullName, path, StringComparison.OrdinalIgnoreCase))
                        {
                            drv = d;
                            loopState.Break();
                        }
                    }

                });

                if (drv != null)
                {
                    drive = drv;
                    drv = null;
                    fileType = PathType.Drive;
                }
                else if (Directory.Exists(path))
                    fileType = PathType.Directory;
                else if (File.Exists(path))
                    fileType = PathType.File;
                else
                    fileType = PathType.Unknown;
            }
            return fileType;
        }


    }
}